package com.hollycrm.sip.proxy;

import com.hollycrm.rtp.RtpProxy;
import com.hollycrm.sip.proxy.config.SPConfig;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

public class UserProxyHandler implements Runnable {
    private static Logger log = Logger.getLogger(UserProxyHandler.class
            .getName());
    private DatagramSocket socket = null;
    private Hashtable<String, String> peers = new Hashtable<>();
    private List<String> peerExtens = new ArrayList<>();
    private Hashtable<String, RtpProxy> calls = new Hashtable<>();
    private volatile boolean mRun = true;

    public UserProxyHandler(DatagramSocket socket) {
        this.socket = socket;
    }

    public void setRun(boolean enable) {
        mRun = enable;
    }

    @Override
    public void run() {

        if (socket == null) {
            return;
        }

        byte[] receiveData = new byte[SipProxy.MAX_DATAGRAM_SIZE];
        DatagramPacket packet = new DatagramPacket(receiveData,
                receiveData.length);

        while (mRun && socket != null) {
            try {
                packet.setLength(SipProxy.MAX_DATAGRAM_SIZE);
                socket.receive(packet);

                int packetLength = packet.getLength();
                // Read bytes and put it in a eueue.
                byte[] bytes = packet.getData();
                byte[] msgBytes = new byte[packetLength];
                System.arraycopy(bytes, 0, msgBytes, 0, packetLength);

                String fromIp = packet.getAddress().getHostAddress();
                int fromPort = packet.getPort();
                // message from asterisk, encrypt it and send to client
                // 如果 从外部来的sip 先解密
                if (fromIp.equals(SPConfig.getInstance().agentIp)) {
                    String rc4Message = new String(msgBytes, "UTF-8");
                    if (rc4Message.length() == 4) {
                        continue;
                    } else {
                        RC4 rc4 = new RC4(SPConfig.getInstance().encryptKey);
                        byte[] data = rc4.rc4(msgBytes);
                        rc4Message = new String(data, "UTF-8");
                        log.info("From proxy: " + fromIp + ":" + fromPort
                                        + "-----------------\r\n" + rc4Message
                                        + "\r\n" + " msg end \r\n");
                    }
                    SipMessage sip = new SipMessage();
                    sip.paser(rc4Message);
                    if (SPConfig.getInstance().encryptRTP) {
                        if (sip.getRequest().startsWith("INVITE")) {
                            addCall(sip);
                        } else if (sip.getRequest()
                                .startsWith("SIP/2.0 200 OK")) {
                            updateCall(sip);
                        } else if (sip.getRequest().startsWith("SIP/2.0 4")) {
                            endCall(sip);
                        } else if (sip.getRequest().startsWith("SIP/2.0 5")) {
                            endCall(sip);
                        } else if (sip.getRequest().startsWith("BYE")
                                || sip.getRequest().startsWith("CANCEL")) {
                            endCall(sip);
                        }
                    }

                    // SIP/2.0/UDP 10.8.103.42:5060;received=218.241.181.25
                    To to = new To(sip.getHeader("To"));
                    String exten = to.getExten();
                    if (!peers.containsKey(exten)) {
                        From from = new From(sip.getHeader("From"));
                        exten = from.getExten();
                        if (!peers.containsKey(exten)) {
                            continue;
                        }
                    }

                    //
//                    if (sip.getHeader("Via") != null) {
//                        Via via = new Via(sip.getHeader("Via"));
//                        via.setPort(SPConfig.getInstance().port);
//                        sip.addHeader("Via", via.getString());
//                    }
                    if (sip.getHeader("Contact") != null) {
                        Contact contact = new Contact(sip.getHeader("Contact"));
                        contact.setPort(SPConfig.getInstance().port);
                        sip.addHeader("Contact", contact.getString());
                    }
                    String[] a = peers.get(exten).split(":");
                    String ip = a[0];
                    int port = Integer.valueOf(a[1]);
                    InetSocketAddress add = new InetSocketAddress(ip, port);
                    DatagramPacket dp = new DatagramPacket(new byte[0], 0, add);
                    String strdata = sip.getSipMessage();
                    byte[] d = strdata.getBytes("UTF-8");
                    // RC4 rc4 = new RC4(SPConfig.getInstance().encryptKey);
                    // byte[] data = rc4.rc4(d);
                    dp.setData(d);
                    dp.setLength(d.length);
                    log.info("Send to [" + ip + ":" + port + "]: \r\n"
                            + strdata + "\r\n msg end \r\n");
//                    System.out.println("Send to [" + ip + ":" + port + "]: \r\n"
//                            + strdata);
                    socket.send(dp);
                    continue;
                }
                // message from client, dencrypt it and send to asterisk
                // 否则就是 pjsip 发送的消息
                else {
                    String rc4Message = new String(msgBytes, "UTF-8");
                    String sipMessage = rc4Message;
                    if (rc4Message.length() <= 4) {
                        log.log(Level.INFO,"--too small message: "+ rc4Message);
                        continue;
                    }
                    SipMessage sip = new SipMessage();
                    sip.paser(sipMessage);
                    if (SPConfig.getInstance().encryptRTP) {
                        if (sip.getRequest().startsWith("INVITE")) {
                            addCall(sip);
                        } else if (sip.getRequest().startsWith("SIP/2.0 200 OK")) {
                            updateCall(sip);
                        } else if (sip.getRequest().startsWith("SIP/2.0 4")) {
                            endCall(sip);
                        } else if (sip.getRequest().startsWith("SIP/2.0 5")) {
                            endCall(sip);
                        } else if (sip.getRequest().startsWith("BYE")
                                || sip.getRequest().startsWith("CANCEL")) {
                            endCall(sip);
                        }
                    }
                    From from = new From(sip.getHeader("From"));
                    String exten = from.getExten();
                    if (sip.getRequest().startsWith("REGISTER")) {
                        peerExtens.add(exten);
                        peers.put(exten, fromIp + ":" + fromPort);
                    }
                    if (sip.getHeader("Via") != null) {
                        Via via = new Via(sip.getHeader("Via"));
                        via.setPort(SPConfig.getInstance().port);
                        via.setIp(SPConfig.getInstance().ip);
                        // newly add more one Via header for this
                        sip.addHeader("Via", via.getString());
                    }
                    if (sip.getHeader("Contact") != null) {
                        Contact contact = new Contact(sip.getHeader("Contact"));
                        contact.setPort(SPConfig.getInstance().port);
                        contact.setIp(SPConfig.getInstance().ip);
                        sip.addHeader("Contact", contact.getString());
                    }
                    InetSocketAddress address = new InetSocketAddress(SPConfig
                            .getInstance().agentIp,
                            SPConfig.getInstance().agentProt);
                    DatagramPacket dp = new DatagramPacket(new byte[0], 0,
                            address);
                    String strdata = sip.getSipMessage();
                    byte[] d = strdata.getBytes("UTF-8");
                     RC4 rc4 = new RC4(SPConfig.getInstance().encryptKey);
                     byte[] data = rc4.rc4(d);
                    dp.setData(data);
                    dp.setLength(data.length);
                    log.info("Send to [" + SPConfig.getInstance().agentIp + ":"
                            + SPConfig.getInstance().agentProt + "]: \r\n"
                            + strdata);
//                    System.out.println("Send to [" + SPConfig.getInstance().agentIp + ":"
//                            + SPConfig.getInstance().agentProt + "]: \r\n"
//                            + strdata);
                    socket.send(dp);
                }
            } catch (Exception e) {
                log.log(Level.WARNING, "", e);
                break;
            } finally {

            }
        }

    }

    // INVITE
    private void addCall(SipMessage message) {
        String callID = message.getHeader("Call-ID");
        if (callID == null)
            return;
        String userAgent = message.getHeader("User-Agent");

        RtpProxy call = new RtpProxy(callID);
        call.start();
        // INVITE from asterisk
        if (userAgent != null && userAgent.equals("Asterisk PBX")) {
            call.setAstIp(message.getSdp().getConnection_address());
            call.setAstPort(message.getSdp().getPort());
        }
        // INVITE from client
        else {
            call.setAgentIp(message.getSdp().getConnection_address());
            call.setAgentPort(message.getSdp().getPort());
        }
        message.getSdp().setConnection_address(SPConfig.getInstance().ip);
        message.getSdp().setPort(call.getListenPort());
        calls.put(callID, call);
    }

    // 200 ok
    private void updateCall(SipMessage message) {
        String callID = message.getHeader("Call-ID");
        if (callID == null)
            return;
        String userAgent = message.getHeader("User-Agent");
        RtpProxy call = calls.get(callID);
        if (call == null) {
            return;
        }
        // INVITE from asterisk
        if (userAgent != null && userAgent.equals("Asterisk PBX")) {
            call.setAstIp(message.getSdp().getConnection_address());
            call.setAstPort(message.getSdp().getPort());
        }
        // INVITE from client
        else {
            call.setAgentIp(message.getSdp().getConnection_address());
            call.setAgentPort(message.getSdp().getPort());
        }
        message.getSdp().setConnection_address(SPConfig.getInstance().ip);
        message.getSdp().setPort(call.getListenPort());
    }

    private void endCall(SipMessage message) {
        String callID = message.getHeader("Call-ID");
        if (callID == null)
            return;
        RtpProxy call = calls.remove(callID);
        if (call != null) {
            call.stop();
        }
    }

}
